<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<meta name="keywords" content="nacos1.3.0,内核" />
	<meta name="description" content="Nacos 1.3.0 全新内核构建过程" />
	<!-- 网页标签标题 -->
	<title>Nacos 1.3.0 全新内核构建过程</title>
	<link rel="shortcut icon" href="https://img.alicdn.com/tfs/TB1hgJpHAPoK1RjSZKbXXX1IXXa-64-64.png"/>
	<link rel="stylesheet" href="/build/blogDetail.css" />
</head>
<body>
	<div id="root"><div class="blog-detail-page" data-reactroot="" data-reactid="1" data-react-checksum="478019918"><header class="header-container header-container-normal" data-reactid="2"><div class="header-body" data-reactid="3"><a href="/zh-cn/index.html" data-reactid="4"><img class="logo" src="/img/nacos_colorful.png" data-reactid="5"/></a><div class="search search-normal" data-reactid="6"><span class="icon-search" data-reactid="7"></span></div><span class="language-switch language-switch-normal" data-reactid="8">En</span><div class="header-menu" data-reactid="9"><img class="header-menu-toggle" src="/img/menu_gray.png" data-reactid="10"/><ul data-reactid="11"><li class="menu-item menu-item-normal" data-reactid="12"><a href="/zh-cn/index.html" data-reactid="13">首页</a></li><li class="menu-item menu-item-normal" data-reactid="14"><a href="/zh-cn/docs/what-is-nacos.html" data-reactid="15">文档</a></li><li class="menu-item menu-item-normal menu-item-normal-active" data-reactid="16"><a href="/zh-cn/blog/index.html" data-reactid="17">博客</a></li><li class="menu-item menu-item-normal" data-reactid="18"><a href="/zh-cn/community/index.html" data-reactid="19">社区</a></li><li class="menu-item menu-item-normal" data-reactid="20"><a href="https://cn.aliyun.com/product/aliware/mse?spm=nacos-website.topbar.0.0.0" data-reactid="21">企业版Nacos</a><img class="menu-img" src="https://img.alicdn.com/tfs/TB1esl_m.T1gK0jSZFrXXcNCXXa-200-200.png" data-reactid="22"/></li><li class="menu-item menu-item-normal" data-reactid="23"><a href="https://job.alibaba.com/zhaopin/position_detail.htm?trace=qrcode_share&amp;positionCode=GP633620" data-reactid="24">招贤纳士</a><img class="menu-img" src="https://img.alicdn.com/tfs/TB1esl_m.T1gK0jSZFrXXcNCXXa-200-200.png" data-reactid="25"/></li><li class="menu-item menu-item-normal" data-reactid="26"><a href="http://console.nacos.io/nacos/index.html" data-reactid="27">控制台样例</a></li></ul></div></div></header><section class="blog-content markdown-body" data-reactid="28"><h1>Nacos 1.3.0 特性以及功能使用文档</h1>
<p><a name="0YIG0"></a></p>
<h2>概述</h2>
<p>本次1.3.0的改动程度很大，涉及两大模块的修改以及新增一个核心模块</p>
<ol>
<li>nacos-core模块修改
<ol>
<li>nacos集群节点成员寻址模式的统一管理</li>
<li>nacos内部事件机制</li>
<li>nacos一致性协议层</li>
</ol>
</li>
<li>nacos-config模块修改
<ol>
<li>新增内嵌分布式数据存储组件</li>
<li>内嵌存储与外置存储细分</li>
<li>内嵌存储简单运维</li>
</ol>
</li>
<li>nacos-consistency模块新增
<ol>
<li>对于AP协议以及CP协议的统一抽象</li>
</ol>
</li>
</ol>
<br />
<a name="rnkDY"></a>
## 系统参数变化
<a name="1Gmg9"></a>
### 新增
<table>
<thead>
<tr>
<th style="text-align:center"><strong>core模块</strong></th>
<th>nacos.watch-file.max-dirs</th>
<th>JVM参数</th>
<th>最大可监听目录数量</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"></td>
<td>nacos.core.notify.ring-buffer-size</td>
<td>JVM参数</td>
<td>快速通知队列的最大长度</td>
</tr>
<tr>
<td style="text-align:center"></td>
<td>nacos.core.notify.share-buffer-size</td>
<td>JVM参数</td>
<td>慢速通知队列的最大长度</td>
</tr>
<tr>
<td style="text-align:center"></td>
<td>nacos.core.member.fail-access-cnt</td>
<td>JVM参数、application.properties配置</td>
<td>集群成员节点最大失败访问次数</td>
</tr>
<tr>
<td style="text-align:center"></td>
<td>nacos.core.address-server.retry</td>
<td>JVM参数、application.properties配置</td>
<td>地址服务器寻址模式，首次启动请求重试次数</td>
</tr>
</tbody>
</table>
<br />
<a name="kxo8O"></a>
## Nacos的未来整体逻辑架构及其组件
![1561217775318-6e408805-18bb-4242-b4e9-83c5b929b469.png](https://cdn.nlark.com/yuque/0/2020/png/333972/1587129046320-5a286f38-8db4-4e76-9b42-8bd859f51a60.png#align=left&display=inline&height=1184&margin=%5Bobject%20Object%5D&name=1561217775318-6e408805-18bb-4242-b4e9-83c5b929b469.png&originHeight=1184&originWidth=1608&size=279074&status=done&style=none&width=1608)
<a name="Hyc6u"></a>
## Nacos集群成员节点寻址模式
<p><br />在1.3.0之前，nacos的naming模块以及config模块存在各自的集群成员节点列表管理任务。为了统一nacos集群下成员列表的寻址模式，将集群节点管理的实现从naming模块以及config模块剥离出来，统一下沉到了core模块的寻址模，同时新增命令行参数 **-Dnacos.member.list **进行设置nacos集群节点列表，该参数可以看作是cluster.conf文件的一个替代。目前nacos的寻址模式类别如下</p>
<ol>
<li>单机模式下：StandaloneMemberLookup</li>
<li>集群模式
<ol>
<li>cluster.conf文件存在：FileConfigMemberLookup</li>
<li>cluster.conf文件不存在或者 -Dnacos.member.list没有设置：AddressServerMemberLookup</li>
</ol>
</li>
</ol>
<p>如果说想指定某一种寻址模式，则设置此参数：<strong>nacos.core.member.lookup.type=[file,address-server]</strong></p>
<p>逻辑图如下
<img src="https://cdn.nlark.com/yuque/__puml/e209a677aa8b5ffce23589e987ee5129.svg#lake_card_v2=eyJjb2RlIjoiQHN0YXJ0dW1sXG5cbigqKSAtLT4gXCJMb29rdXBGYWN0b3J5LmluaXQoKVwiXG5cbmlmIFwic3RhbmRhbG9uZSBtb2RlXCIgdGhlblxuICAtLT5bdHJ1ZV0gXCJyZXR1cm4gU3RhbmRhbG9uZU1lbWJlckxvb2t1cFwiXG4gIC0tPiBMb29rdXAucnVuKClcbmVsc2VcbiBpZiBcImNsdXN0ZXIuY29uZiBleGlzdHNcIiB0aGVuXG4gICAgLS0-W3RydWVdIFwicmV0dXJuIEZpbGVDb25maWdNZW1iZXJMb29rdXBcIlxuICAgIC0tPiBMb29rdXAucnVuKClcbiBlbHNlXG4gICAgLT5bZmFsc2VdIFwicmV0dXJuIEFkZHJlc3NTZXJ2ZXJNZW1iZXJMb29rdXBcIlxuICAgIC0tPiBMb29rdXAucnVuKClcbiBlbmRpZlxuZW5kaWZcblxuLS0-ICgqKVxuXG5AZW5kdW1sIiwidHlwZSI6InB1bWwiLCJpZCI6IlplWGtkIiwidXJsIjoiaHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlL19fcHVtbC9lMjA5YTY3N2FhOGI1ZmZjZTIzNTg5ZTk4N2VlNTEyOS5zdmciLCJjYXJkIjoiZGlhZ3JhbSJ9" alt=""></p>
<p><a name="wqkMp"></a></p>
<h3>寻址模式详细</h3>
<p>接下来介绍除了单机模式下的寻址模式的其他两种寻址模式<br /></p>
<p><a name="egl3H"></a></p>
<h4>FileConfigMemberLookup</h4>
<p>该寻址模式是基于cluster.conf文件进行管理的，每个节点会读取各自${nacos.home}/conf下的cluster.conf文件内的成员节点列表，然后组成一个集群。并且在首次读取完${nacos.home}/conf下的cluster.conf文件后，会自动向操作系统的_<strong>inotify</strong>_机制注册一个目录监听器，监听${nacos.home}/conf目录下的所有文件变动（注意，这里只会监听文件，对于子目录下的文件变动无法监听）<br />当需要进行集群节点扩缩容时，需要手动去修改每个节点各自${nacos.home}/conf下的cluster.conf的成员节点列表内容。</p>
<pre><code class="language-java"><span class="hljs-keyword">private</span> FileWatcher watcher = <span class="hljs-keyword">new</span> FileWatcher() {
		<span class="hljs-meta">@Override</span>
		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onChange</span><span class="hljs-params">(FileChangeEvent event)</span> </span>{
			readClusterConfFromDisk();
		}

		<span class="hljs-meta">@Override</span>
		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">interest</span><span class="hljs-params">(String context)</span> </span>{
			<span class="hljs-keyword">return</span> StringUtils.contains(context, <span class="hljs-string">"cluster.conf"</span>);
		}
};

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">start</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> NacosException </span>{
	readClusterConfFromDisk();

	<span class="hljs-keyword">if</span> (memberManager.getServerList().isEmpty()) {
		<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NacosException(NacosException.SERVER_ERROR,
					<span class="hljs-string">"Failed to initialize the member node, is empty"</span>);
	}

	<span class="hljs-comment">// Use the inotify mechanism to monitor file changes and automatically</span>
	<span class="hljs-comment">// trigger the reading of cluster.conf</span>
	<span class="hljs-keyword">try</span> {
		WatchFileCenter.registerWatcher(ApplicationUtils.getConfFilePath(), watcher);
	}
	<span class="hljs-keyword">catch</span> (Throwable e) {
		Loggers.CLUSTER.error(<span class="hljs-string">"An exception occurred in the launch file monitor : {}"</span>, e);
	}
}
</code></pre>
<p>首次启动时直接读取cluster.conf文件内的节点列表信息，然后向WatchFileCenter注册一个目录监听器，当cluster.conf文件发生变动时自动触发_<strong>readClusterConfFromDisk()</strong>_重新读取cluster.conf文件<br /><img src="https://cdn.nlark.com/yuque/0/2020/png/333972/1591014354207-49c1e934-2aa5-465a-8a2b-0b09902814f8.png#align=left&amp;display=inline&amp;height=531&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=531&amp;originWidth=1169&amp;size=105696&amp;status=done&amp;style=none&amp;width=1169" alt="image.png">
<a name="yFTCl"></a></p>
<h4>AddressServerMemberLookup</h4>
<p>该寻址模式是基于一个额外的web服务器来管理cluster.conf，每个节点定期向该web服务器请求cluster.conf的文件内容，然后实现集群节点间的寻址，以及扩缩容。<br />当需要进行集群扩缩容时，只需要修改cluster.conf文件即可，然后每个节点向地址服务器请求时会自动的得到最新的cluster.conf文件内容。</p>
<pre><code class="language-java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">start</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> NacosException </span>{
	<span class="hljs-keyword">if</span> (start.compareAndSet(<span class="hljs-keyword">false</span>, <span class="hljs-keyword">true</span>)) {
		<span class="hljs-keyword">this</span>.maxFailCount = Integer.parseInt(ApplicationUtils.getProperty(<span class="hljs-string">"maxHealthCheckFailCount"</span>, <span class="hljs-string">"12"</span>));
		initAddressSys();
		run();
	}
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initAddressSys</span><span class="hljs-params">()</span> </span>{
	String envDomainName = System.getenv(<span class="hljs-string">"address_server_domain"</span>);
	<span class="hljs-keyword">if</span> (StringUtils.isBlank(envDomainName)) {
		domainName = System.getProperty(<span class="hljs-string">"address.server.domain"</span>, <span class="hljs-string">"jmenv.tbsite.net"</span>);
	} <span class="hljs-keyword">else</span> {
		domainName = envDomainName;
	}
	String envAddressPort = System.getenv(<span class="hljs-string">"address_server_port"</span>);
	<span class="hljs-keyword">if</span> (StringUtils.isBlank(envAddressPort)) {
		addressPort = System.getProperty(<span class="hljs-string">"address.server.port"</span>, <span class="hljs-string">"8080"</span>);
	} <span class="hljs-keyword">else</span> {
		addressPort = envAddressPort;
	}
	addressUrl = System.getProperty(<span class="hljs-string">"address.server.url"</span>,
				ApplicationUtils.getContextPath() + <span class="hljs-string">"/"</span> + <span class="hljs-string">"serverlist"</span>);
	addressServerUrl = <span class="hljs-string">"http://"</span> + domainName + <span class="hljs-string">":"</span> + addressPort + addressUrl;
	envIdUrl = <span class="hljs-string">"http://"</span> + domainName + <span class="hljs-string">":"</span> + addressPort + <span class="hljs-string">"/env"</span>;

	Loggers.CORE.info(<span class="hljs-string">"ServerListService address-server port:"</span> + addressPort);
	Loggers.CORE.info(<span class="hljs-string">"ADDRESS_SERVER_URL:"</span> + addressServerUrl);
}

<span class="hljs-meta">@SuppressWarnings</span>(<span class="hljs-string">"PMD.UndefineMagicConstantRule"</span>)
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> NacosException </span>{
	<span class="hljs-comment">// With the address server, you need to perform a synchronous member node pull at startup</span>
	<span class="hljs-comment">// Repeat three times, successfully jump out</span>
	<span class="hljs-keyword">boolean</span> success = <span class="hljs-keyword">false</span>;
	Throwable ex = <span class="hljs-keyword">null</span>;
	<span class="hljs-keyword">int</span> maxRetry = ApplicationUtils.getProperty(<span class="hljs-string">"nacos.core.address-server.retry"</span>, Integer.class, <span class="hljs-number">5</span>);
	<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; maxRetry; i ++) {
		<span class="hljs-keyword">try</span> {
			syncFromAddressUrl();
			success = <span class="hljs-keyword">true</span>;
			<span class="hljs-keyword">break</span>;
		} <span class="hljs-keyword">catch</span> (Throwable e) {
			ex = e;
			Loggers.CLUSTER.error(<span class="hljs-string">"[serverlist] exception, error : {}"</span>, ExceptionUtil.getAllExceptionMsg(ex));
		}
	}
	<span class="hljs-keyword">if</span> (!success) {
		<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NacosException(NacosException.SERVER_ERROR, ex);
	}

	GlobalExecutor.scheduleByCommon(<span class="hljs-keyword">new</span> AddressServerSyncTask(), <span class="hljs-number">5_000L</span>);
}
</code></pre>
<p>在初始化时，会主动去向地址服务器同步当前的集群成员列表信息，如果失败则进行重试，其最大重试次数可通过设置_<strong>nacos.core.address-server.retry</strong>_来控制，默认是5次，然后成功之后，将创建定时任务去向地址服务器同步集群成员节点信息<br /><img src="https://cdn.nlark.com/yuque/0/2020/png/333972/1591014362972-004f5338-af0d-4d0d-b769-4f3d5118c08a.png#align=left&amp;display=inline&amp;height=846&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=846&amp;originWidth=1149&amp;size=188886&amp;status=done&amp;style=none&amp;width=1149" alt="image.png">
<a name="sgOTI"></a></p>
<h3>节点管理和寻址模式如何结合的</h3>
<p><img src="https://cdn.nlark.com/yuque/0/2020/png/333972/1591014400580-39b83aa0-c548-4241-a49e-0d72abda2a95.png#align=left&amp;display=inline&amp;height=715&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=715&amp;originWidth=1189&amp;size=131826&amp;status=done&amp;style=none&amp;width=1189" alt="image.png"><br />MemberLookup在启动之后，会根据不同的寻址模式，执行寻址任务，将收集到集群节点列表信息，调用memberChange，触发集群节点变动，然后发布节点变更事件
<a name="idHpC"></a></p>
<h2>Nacos一致性协议协议层抽象</h2>
<p>从nacos的未来的整体架构图可以看出，一致性协议层将是作为nacos的最为核心的模块，将服务于构建在core模块之上的各个功能模块，或者服务与core模块本身。而一致性协议因为分区容错性的存在，需要在可用性与一致性之间做选择，因此就存在两大类一致性：最终一致性和强一致性。在nacos中，这两类一致性协议都是可能用到的，比如naming模块，对于服务实例的数据管理分别用到了AP以及CP，而对于config模块，将会涉及使用CP。同时还有如下几个功能需求点</p>
<ol>
<li>目前持久化服务使用用了变种版本的raft，并且业务和raft协议耦合，因此需要抽离解耦，同时是选择一个标准的Java版Raft实现</li>
<li>对于中小用户，配置基本其实不会超级多，独立一个mysql，相对重一些，需要一个轻量化的存储方案，并且支持2.0不依赖mysql和3.0依赖mysql可配置能力</li>
<li>由于CP或者AP，其存在多种实现，如何对一致性协议层做一次很好的抽象，以便将来可以快速的实现底层一致性协议具体实现的替换，比如Raft协议，目前nacos的选型是JRaft，不排除将来nacos会自己实现一个标准raft协议或者实现Paxos协议</li>
<li>由于Nacos存在多个独立工作的功能模块，每个功能模块之间不能出现影响，比如A模块处理请求过慢或者出现异常时，不能影响B模块的正常工作，即每个功能模块在使用一致性协议时，如何将每个模块的数据处理进行隔离？</li>
</ol>
<p>根据一致协议以及上述功能需求点，本次做了一个抽象的一致协议层以及相关的接口
<a name="3w8xM"></a></p>
<h3>一致协议抽象</h3>
<p><a name="p7zRo"></a></p>
<h4>ConsistencyProtocol</h4>
<p>所谓一致性，即多个副本之间是否能够保持一致性的特性，而副本的本质就是数据，对数据的操作，不是获取就是修改。同时，一致协议其实是针对分布式情况的，而这必然涉及多个节点，因此，需要有相应的接口能够调整一致性协议的协同工作节点。如果我们要观察一致性协议运行的情况，该怎么办？比如Raft协议，我们希望得知当前集群中的Leader是谁，任期的情况，当前集群中的成员节点有谁？因此，还需要提供一个一致性协议元数据获取。<br />综上所述，ConsistencyProtcol的大致设计可以出来了</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ConsistencyProtocol</span>&lt;<span class="hljs-title">T</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Config</span>, <span class="hljs-title">P</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LogProcessor</span>&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title">CommandOperations</span> </span>{

    <span class="hljs-comment">/**
     * Consistency protocol initialization: perform initialization operations based on the incoming Config
     * 一致性协议初始化，根据 Config 实现类
     *
     * <span class="hljs-doctag">@param</span> config {<span class="hljs-doctag">@link</span> Config}
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">(T config)</span></span>;

    <span class="hljs-comment">/**
     * Add a log handler
     *
     * <span class="hljs-doctag">@param</span> processors {<span class="hljs-doctag">@link</span> LogProcessor}
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">addLogProcessors</span><span class="hljs-params">(Collection&lt;P&gt; processors)</span></span>;

    <span class="hljs-comment">/**
     * Copy of metadata information for this consensus protocol
     * 该一致性协议的元数据信息
     *
     * <span class="hljs-doctag">@return</span> metaData {<span class="hljs-doctag">@link</span> ProtocolMetaData}
     */</span>
    <span class="hljs-function">ProtocolMetaData <span class="hljs-title">protocolMetaData</span><span class="hljs-params">()</span></span>;

    <span class="hljs-comment">/**
     * Obtain data according to the request
     *
     * <span class="hljs-doctag">@param</span> request request
     * <span class="hljs-doctag">@return</span> data {<span class="hljs-doctag">@link</span> Response}
     * <span class="hljs-doctag">@throws</span> Exception
     */</span>
    <span class="hljs-function">Response <span class="hljs-title">getData</span><span class="hljs-params">(GetRequest request)</span> <span class="hljs-keyword">throws</span> Exception</span>;

    <span class="hljs-comment">/**
     * Get data asynchronously
     *
     * <span class="hljs-doctag">@param</span> request request
     * <span class="hljs-doctag">@return</span> data {<span class="hljs-doctag">@link</span> CompletableFuture&lt;Response&gt;}
     */</span>
    <span class="hljs-function">CompletableFuture&lt;Response&gt; <span class="hljs-title">aGetData</span><span class="hljs-params">(GetRequest request)</span></span>;

    <span class="hljs-comment">/**
     * Data operation, returning submission results synchronously
     * 同步数据提交，在 Datum 中已携带相应的数据操作信息
     *
     * <span class="hljs-doctag">@param</span> data {<span class="hljs-doctag">@link</span> Log}
     * <span class="hljs-doctag">@return</span> submit operation result {<span class="hljs-doctag">@link</span> Response}
     * <span class="hljs-doctag">@throws</span> Exception
     */</span>
    <span class="hljs-function">Response <span class="hljs-title">submit</span><span class="hljs-params">(Log data)</span> <span class="hljs-keyword">throws</span> Exception</span>;

    <span class="hljs-comment">/**
     * Data submission operation, returning submission results asynchronously
     * 异步数据提交，在 Datum 中已携带相应的数据操作信息，返回一个Future，自行操作，提交发生的异常会在CompleteFuture中
     *
     * <span class="hljs-doctag">@param</span> data {<span class="hljs-doctag">@link</span> Log}
     * <span class="hljs-doctag">@return</span> {<span class="hljs-doctag">@link</span> CompletableFuture&lt;Response&gt;} submit result
     * <span class="hljs-doctag">@throws</span> Exception when submit throw Exception
     */</span>
    <span class="hljs-function">CompletableFuture&lt;Response&gt; <span class="hljs-title">submitAsync</span><span class="hljs-params">(Log data)</span></span>;

    <span class="hljs-comment">/**
     * New member list
     * 新的成员节点列表，一致性协议自行处理相应的成员节点是加入还是离开
     *
     * <span class="hljs-doctag">@param</span> addresses [ip:port, ip:port, ...]
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">memberChange</span><span class="hljs-params">(Set&lt;String&gt; addresses)</span></span>;

    <span class="hljs-comment">/**
     * Consistency agreement service shut down
     * 一致性协议服务关闭
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">shutdown</span><span class="hljs-params">()</span></span>;

}
</code></pre>
<p>而针对CP协议，由于存在Leader的概念，因此需要提供一个方法用于获取CP协议当前的Leader是谁</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CPProtocol</span>&lt;<span class="hljs-title">C</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Config</span>&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title">ConsistencyProtocol</span>&lt;<span class="hljs-title">C</span>&gt; </span>{

	<span class="hljs-comment">/**
	 * Returns whether this node is a leader node
	 *
	 * <span class="hljs-doctag">@param</span> group business module info
	 * <span class="hljs-doctag">@return</span> is leader
	 * <span class="hljs-doctag">@throws</span> Exception
	 */</span>
	<span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">isLeader</span><span class="hljs-params">(String group)</span> <span class="hljs-keyword">throws</span> Exception</span>;

}
</code></pre>
<p><a name="oJFpB"></a></p>
<h4>数据操作请求提交对象：Log、GetRequest</h4>
<p>上面说到，一致性协议其实是对于数据操作而言的，数据操作基本分为两大类：数据查询以及数据修改，同时还要满足不同功能模块之间的数据进行隔离。因此这里针对数据修改操作以及数据查询操作分别阐述。</p>
<ol>
<li>数据修改
<ol>
<li>数据修改操作，一定要知道本次请求是属于哪一个功能模块的</li>
<li>数据修改操作，首先一定要知道这个数据的修改操作具体是哪一种修改操作，方便功能模块针对真正的数据修改操作进行相应的逻辑操作</li>
<li>数据修改操作，一定要知道修改的数据是什么，即请求体，为了使得一致性协议层更为通用，这里对于请求体的数据结构，选择了byte[]数组</li>
<li>数据的类型，由于我们将真正的数据序列化为了byte[]数组，为了能够正常序列化，我们可能还需要记录这个数据的类型是什么</li>
<li>本次请求的信息摘要或者标识信息</li>
<li>本次请求的额外信息，用于将来扩展需要传输的数据</li>
</ol>
</li>
</ol>
<p>综上，可以得出Log对象的设计如下</p>
<pre><code class="language-protobuf"><span class="hljs-class"><span class="hljs-keyword">message</span> <span class="hljs-title">Log</span> </span>{
	<span class="hljs-comment">// 功能模块分组信息</span>
    <span class="hljs-built_in">string</span> <span class="hljs-keyword">group</span> = <span class="hljs-number">1</span>;
    <span class="hljs-comment">// 摘要或者标识</span>
    <span class="hljs-built_in">string</span> key = <span class="hljs-number">2</span>;
    <span class="hljs-comment">// 具体请求数据</span>
    <span class="hljs-built_in">bytes</span> data = <span class="hljs-number">3</span>;
    <span class="hljs-comment">// 数据类型</span>
    <span class="hljs-built_in">string</span> type = <span class="hljs-number">4</span>;
    <span class="hljs-comment">// 更为具体的数据操作</span>
    <span class="hljs-built_in">string</span> operation = <span class="hljs-number">5</span>;
    <span class="hljs-comment">// 额外信息</span>
    map&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt; extendInfo = <span class="hljs-number">6</span>;
}
</code></pre>
<ol start="2">
<li>数据查询
<ol>
<li>数据查询操作，一定要知道本次请求是由哪一个功能模块发起的</li>
<li>数据查询的条件是什么，为了兼容各种存储结构的数据查询操作，这里用byte[]进行存储</li>
<li>本次请求的额外信息，用于将来扩展需要传输的数据</li>
</ol>
</li>
</ol>
<p>综上，可以得出GetRequest对象的设计如下</p>
<pre><code class="language-protobuf"><span class="hljs-class"><span class="hljs-keyword">message</span> <span class="hljs-title">GetRequest</span> </span>{
	<span class="hljs-comment">// 功能模块分组信息</span>
    <span class="hljs-built_in">string</span> <span class="hljs-keyword">group</span> = <span class="hljs-number">1</span>;
    <span class="hljs-comment">// 具体请求数据</span>
    <span class="hljs-built_in">bytes</span> data = <span class="hljs-number">2</span>;
    <span class="hljs-comment">// 额外信息</span>
    map&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt; extendInfo = <span class="hljs-number">3</span>;
}
</code></pre>
<p><a name="vBig4"></a></p>
<h4>功能模块使用一致性协议：LogProcessor</h4>
<p>当数据操作通过一致性协议进行submit之后，每个节点需要去处理这个Log或者GetRequest对象，因此，我们需要抽象出一个Log、GetRequest对象的Processor，不同的功能模块通过实现该处理器，ConsistencyProtocol内部会根据Log、GetRequest的group属性，将Log、GetRequest对象路由到具体的Processor，当然，Processor也需要表明自己是属于哪一个功能模块的。</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LogProcessor</span> </span>{

    <span class="hljs-comment">/**
     * get data by key
     *
     * <span class="hljs-doctag">@param</span> request request {<span class="hljs-doctag">@link</span> GetRequest}
     * <span class="hljs-doctag">@return</span> target type data
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> Response <span class="hljs-title">onRequest</span><span class="hljs-params">(GetRequest request)</span></span>;

    <span class="hljs-comment">/**
     * Process Submitted Log
     *
     * <span class="hljs-doctag">@param</span> log {<span class="hljs-doctag">@link</span> Log}
     * <span class="hljs-doctag">@return</span> {<span class="hljs-doctag">@link</span> boolean}
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> Response <span class="hljs-title">onApply</span><span class="hljs-params">(Log log)</span></span>;

    <span class="hljs-comment">/**
     * Irremediable errors that need to trigger business price cuts
     *
     * <span class="hljs-doctag">@param</span> error {<span class="hljs-doctag">@link</span> Throwable}
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onError</span><span class="hljs-params">(Throwable error)</span> </span>{
    }

    <span class="hljs-comment">/**
     * In order for the state machine that handles the transaction to be able to route
     * the Log to the correct LogProcessor, the LogProcessor needs to have an identity
     * information
     *
     * <span class="hljs-doctag">@return</span> Business unique identification name
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> String <span class="hljs-title">group</span><span class="hljs-params">()</span></span>;

}
</code></pre>
<p>针对CP协议，比如Raft协议，存在快照的设计，因此我们需要针对CP协议单独扩展出一个方法</p>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LogProcessor4CP</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LogProcessor</span> </span>{

    <span class="hljs-comment">/**
     * Discovery snapshot handler
     * It is up to LogProcessor to decide which SnapshotOperate should be loaded and saved by itself
     *
     * <span class="hljs-doctag">@return</span> {<span class="hljs-doctag">@link</span> List &lt;SnapshotOperate&gt;}
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;SnapshotOperation&gt; <span class="hljs-title">loadSnapshotOperate</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> Collections.emptyList();
    }

}
</code></pre>
<p><a name="zbsAE"></a></p>
<h4>综述</h4>
<p>从上面这几点可以看出来，ConsistencyProtocol是对上层功能模块暴露出来的使用接口，每个ConsistencyProtocol后面有具体的一致性协议实现的Backend，由于Backend无法很好的兼容nacos现有的架构设计，因此额外设计的LogProcessor就是为了解决这个问题。<br /><img src="https://cdn.nlark.com/yuque/0/2020/png/333972/1591015048030-8a4bff4a-20ed-46dd-a7f7-98655b22946f.png#align=left&amp;display=inline&amp;height=591&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=591&amp;originWidth=886&amp;size=93327&amp;status=done&amp;style=none&amp;width=886" alt="image.png"><br />同时，由于在一致性协议层内部的Backend中需要实现对不同业务模块的数据进行隔离处理，而这个一块逻辑由请求对象和LogProcessor的group属性来实现<br /><img src="https://cdn.nlark.com/yuque/0/2020/png/333972/1591015155835-a897262e-8e57-409c-bf94-d57bf765c80b.png#align=left&amp;display=inline&amp;height=591&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=591&amp;originWidth=910&amp;size=118083&amp;status=done&amp;style=none&amp;width=910" alt="image.png">
<a name="C1yU6"></a></p>
<h3>一致性协议层工作流程</h3>
<p>我们可以通过一个时序图看看，一致性协议层的大致工作流程
<img src="https://cdn.nlark.com/yuque/__puml/30b7e270e7aef8bb63136aaffbe5bfbf.svg#lake_card_v2=eyJjb2RlIjoiQHN0YXJ0dW1sXG5cbnBhcnRpY2lwYW50IFwiTWVtYmVyTWFuYWdlclwiIGFzIE5hY29zQ2x1c3RlclxucGFydGljaXBhbnQgXCJCdXNpbmVzc01vZHVsZVwiIGFzIEJpelxucGFydGljaXBhbnQgXCJDb25zaXN0ZW5jeVByb3RvY29sXCIgYXMgUHJvdG9jb2xcbnBhcnRpY2lwYW50IFwiQ0FQQmFja2VuZFwiIGFzIEJhY2tlbmRcbnBhcnRpY2lwYW50IFwiQ29uZmlnXCIgYXMgQ29uZmlnXG5wYXJ0aWNpcGFudCBcIkxvZ1Byb2Nlc3NvclwiIGFzIFByb2Nlc3NvclxucGFydGljaXBhbnQgXCJMb2dcIiBhcyBMb2dcblxuYWN0aXZhdGUgTmFjb3NDbHVzdGVyXG5OYWNvc0NsdXN0ZXIgLT4gTmFjb3NDbHVzdGVyOiBpbml0KCkg5Yid5aeL5YyWTmFjb3Ppm4bnvqRcblxuTmFjb3NDbHVzdGVyIC0-IENvbmZpZzog6I635Y-WQ29uZmln5a-56LGhXG5hY3RpdmF0ZSBDb25maWdcbkNvbmZpZyAtPiBDb25maWc6IOaUtumbhkxvZ1Byb2Nlc3NvcueahOS_oeaBr1xuQ29uZmlnIC0-IE5hY29zQ2x1c3RlclxuZGVhY3RpdmF0ZSBDb25maWdcblxuXG5OYWNvc0NsdXN0ZXIgLT4gUHJvdG9jb2w6IOiOt-WPluaJgOaciUNvbnNpc3RlbmN5UHJvdG9jb2zlrp7njrBcbmFjdGl2YXRlIFByb3RvY29sXG5cbk5hY29zQ2x1c3RlciAtPiBQcm90b2NvbDogaW5pdChDb25maWcpIOaWueazleaJp-ihjFxuXG5kZWFjdGl2YXRlIFByb3RvY29sXG5kZWFjdGl2YXRlIE5hY29zQ2x1c3RlclxuXG5cbkJpeiAtPiBMb2c6IOWIm-W7uuS4gOS4quS6i-WKoeWvueixoVxuYWN0aXZhdGUgQml6XG5hY3RpdmF0ZSBMb2dcblxuXG5Mb2cgLT4gTG9nOiDorr7nva5kYXRhXG5Mb2cgLT4gTG9nOiDorr7nva5rZXlcbkxvZyAtPiBMb2c6IOiuvue9rmNsYXNzTmFtZVxuTG9nIC0-IExvZzog6K6-572uZXh0ZW5kSW5mb1xuTG9nIC0-IEJpelxuZGVhY3RpdmF0ZSBMb2dcblxuQml6IC0-IFByb3RvY29sOiBzdWJtaXQoTG9nKSDosIPnlKjkuIDoh7TmgKfljY_orq7ov5vooYzkuovliqHmj5DkuqRcbmFjdGl2YXRlIFByb3RvY29sXG5cblByb3RvY29sIC0-IEJhY2tlbmQ6IOWGhemDqOS4gOiHtOaAp-WNj-iuruW3peS9nFxuYWN0aXZhdGUgQmFja2VuZFxuXG5CYWNrZW5kIC0-IFByb3RvY29sOiDov5Tlm57lt6XkvZzlpITnkIbnu5PmnpxcbmRlYWN0aXZhdGUgQmFja2VuZFxuXG5Qcm90b2NvbCAtPiBQcm9jZXNzb3I6IOWwhkxvZ-WIhuWPkeWIsOWvueW6lOeahFByb2Nlc3Nvcu-8jOiwg-eUqCBvbkFwcGx5IOaWueazlVxuYWN0aXZhdGUgUHJvdG9jb2xcbmRlYWN0aXZhdGUgUHJvdG9jb2xcblxuUHJvY2Vzc29yIC0-IEJpejog5LqL5Yqh5o-Q5Lqk57uT5p6cXG5cbmRlYWN0aXZhdGUgUHJvY2Vzc29yXG5kZWFjdGl2YXRlIEJpelxuXG5AZW5kdW1sIiwidHlwZSI6InB1bWwiLCJtYXJnaW4iOnRydWUsImlkIjoiNFZpMkwiLCJ1cmwiOiJodHRwczovL2Nkbi5ubGFyay5jb20veXVxdWUvX19wdW1sLzMwYjdlMjcwZTdhZWY4YmI2MzEzNmFhZmZiZTViZmJmLnN2ZyIsImNhcmQiOiJkaWFncmFtIn0=" alt=""><a name="xtBNU"></a></p>
<h3>Nacos一致性协议层之CP协议的实现选择——JRaft</h3>
<p>一致性协议层抽象好之后，剩下就是具体一致性协议实现的选择了，这里我们选择了蚂蚁金服开源的JRaft，那么我们如何将JRaft作为CP协议的一个Backend呢？下面的简单流程图描述了当JRaft作为CP协议的一个Backend时的初始化流程</p>
<pre><code class="language-java"><span class="hljs-comment">/**
 * A concrete implementation of CP protocol: JRaft
 *
 * &lt;pre&gt;
 *                                           ┌──────────────────────┐               
 *                                           │                      │               
 *            ┌──────────────────────┐       │                      ▼               
 *            │   ProtocolManager    │       │        ┌───────────────────────────┐ 
 *            └──────────────────────┘       │        │for p in [LogProcessor4CP] │ 
 *                        │                  │        └───────────────────────────┘ 
 *                        ▼                  │                      │               
 *      ┌──────────────────────────────────┐ │                      ▼               
 *      │    discovery LogProcessor4CP     │ │             ┌─────────────────┐      
 *      └──────────────────────────────────┘ │             │  get p.group()  │      
 *                        │                  │             └─────────────────┘      
 *                        ▼                  │                      │               
 *                 ┌─────────────┐           │                      │               
 *                 │ RaftConfig  │           │                      ▼               
 *                 └─────────────┘           │      ┌──────────────────────────────┐
 *                        │                  │      │  create raft group service   │
 *                        ▼                  │      └──────────────────────────────┘
 *              ┌──────────────────┐         │                                      
 *              │  JRaftProtocol   │         │                                      
 *              └──────────────────┘         │                                      
 *                        │                  │                                      
 *                     init()                │                                      
 *                        │                  │                                      
 *                        ▼                  │                                      
 *               ┌─────────────────┐         │                                      
 *               │   JRaftServer   │         │                                      
 *               └─────────────────┘         │                                      
 *                        │                  │                                      
 *                        │                  │                                      
 *                        ▼                  │                                      
 *             ┌────────────────────┐        │                                      
 *             │JRaftServer.start() │        │                                      
 *             └────────────────────┘        │                                      
 *                        │                  │                                      
 *                        └──────────────────┘                                      
 * &lt;/pre&gt;
 * 
 * <span class="hljs-doctag">@author</span> &lt;a href="mailto:liaochuntao@live.com"&gt;liaochuntao&lt;/a&gt;
 */</span>
</code></pre>
<p>JRaftProtocol是当JRaft作为CP协议的Backend时的一个ConsistencyProtocol的具体实现，其内部有一个JRaftServer成员属性，JRaftServer分装了JRaft的各种API操作，比如数据操作的提交，数据的查询，成员节点的变更，Leader节点的查询等等。</p>
<p><em><strong>注意事项：JRaft运行期间产生的数据在${nacos.home}/data/protocol/raft文件目录下。不同的业务模块有不同的文件分组，如果当节点出现crash或者异常关闭时，清空该目录下的文件，重启节点即可</strong></em></p>
<p>由于JRaft实现了raft group的概念，因此，完全可以利用raft group的设计，为每个功能模块单独创建一个raft group。这里给出部分代码，该代码体现了如何将LogProcessor嵌入到状态机中并为每个LogPrcessor创建一个Raft Group</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title">createMultiRaftGroup</span><span class="hljs-params">(Collection&lt;LogProcessor4CP&gt; processors)</span> </span>{
	<span class="hljs-comment">// There is no reason why the LogProcessor cannot be processed because of the synchronization</span>
	<span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>.isStarted) {
		<span class="hljs-keyword">this</span>.processors.addAll(processors);
		<span class="hljs-keyword">return</span>;
	}

	<span class="hljs-keyword">final</span> String parentPath = Paths
				.get(ApplicationUtils.getNacosHome(), <span class="hljs-string">"data/protocol/raft"</span>).toString();

	<span class="hljs-keyword">for</span> (LogProcessor4CP processor : processors) {
		<span class="hljs-keyword">final</span> String groupName = processor.group();
		<span class="hljs-keyword">if</span> (multiRaftGroup.containsKey(groupName)) {
			<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> DuplicateRaftGroupException(groupName);
		}

		<span class="hljs-comment">// Ensure that each Raft Group has its own configuration and NodeOptions</span>
		Configuration configuration = conf.copy();
		NodeOptions copy = nodeOptions.copy();
		JRaftUtils.initDirectory(parentPath, groupName, copy);

		<span class="hljs-comment">// Here, the LogProcessor is passed into StateMachine, and when the StateMachine</span>
		<span class="hljs-comment">// triggers onApply, the onApply of the LogProcessor is actually called</span>
		NacosStateMachine machine = <span class="hljs-keyword">new</span> NacosStateMachine(<span class="hljs-keyword">this</span>, processor);

		copy.setFsm(machine);
		copy.setInitialConf(configuration);

		<span class="hljs-comment">// Set snapshot interval, default 1800 seconds</span>
		<span class="hljs-keyword">int</span> doSnapshotInterval = ConvertUtils.toInt(raftConfig
							.getVal(RaftSysConstants.RAFT_SNAPSHOT_INTERVAL_SECS),
					RaftSysConstants.DEFAULT_RAFT_SNAPSHOT_INTERVAL_SECS);

		<span class="hljs-comment">// If the business module does not implement a snapshot processor, cancel the snapshot</span>
		doSnapshotInterval = CollectionUtils
					.isEmpty(processor.loadSnapshotOperate()) ? <span class="hljs-number">0</span> : doSnapshotInterval;

		copy.setSnapshotIntervalSecs(doSnapshotInterval);
		Loggers.RAFT.info(<span class="hljs-string">"create raft group : {}"</span>, groupName);
		RaftGroupService raftGroupService = <span class="hljs-keyword">new</span> RaftGroupService(groupName,
					localPeerId, copy, rpcServer, <span class="hljs-keyword">true</span>);

		<span class="hljs-comment">// Because RpcServer has been started before, it is not allowed to start again here</span>
		Node node = raftGroupService.start(<span class="hljs-keyword">false</span>);
		machine.setNode(node);
		RouteTable.getInstance().updateConfiguration(groupName, configuration);
		RaftExecutor.executeByCommon(() -&gt; registerSelfToCluster(groupName, localPeerId, configuration));

		<span class="hljs-comment">// Turn on the leader auto refresh for this group</span>
		Random random = <span class="hljs-keyword">new</span> Random();
		<span class="hljs-keyword">long</span> period = nodeOptions.getElectionTimeoutMs() + random.nextInt(<span class="hljs-number">5</span> * <span class="hljs-number">1000</span>);
		RaftExecutor.scheduleRaftMemberRefreshJob(() -&gt; refreshRouteTable(groupName),
					nodeOptions.getElectionTimeoutMs(), period, TimeUnit.MILLISECONDS);
		multiRaftGroup.put(groupName,
					<span class="hljs-keyword">new</span> RaftGroupTuple(node, processor, raftGroupService, machine));
	}
}
</code></pre>
<p><a name="4czvB"></a></p>
<h4>疑问解答：为什么要创建多个raft group<br /></h4>
<p>或许有的人会有疑问，既然之前已经设计出了LogProcessor，完全可以利用一个Raft Group，在状态机appl时，根据Log的group属性进行路由到不同的LogProcessor即可，每个功能模块就创建一个raft group，不是会消耗大量的资源吗？<br />正如之前所说，我们希望独立工作的模块之间相互不存在影响，比如A模块处理Log因为存在Block操作可能使得apply的速度缓慢，亦或者可能中途发生异常，对于Raft协议来说，当日志apply失败时，状态机将不能够继续向前推进，因为如果继续向前推进的话，由于上一步的apply失败，后面的所有apply都可能失败，将会导致这个节点的数据与其他节点的数据永远不一致。如果说我们将所有独立工作的模块，对于数据操作的请求处理放在同一个raft group，即一个状态机中，就不可避免的会出现上述所说的问题，某个模块在apply日志发生不可控的因素时，会影响其他模块的正常工作。
<a name="2GyRw"></a></p>
<h3>JRaft运维操作</h3>
<p>为了使用者能够对JRaft进行相关简单的运维，比如Leader的切换，重置当前Raft集群成员，触发某个节点进行Snapshot操作等等，提供了一个简单的HTTP接口进行操作，并且该接口有一定的限制，即每次只会执行一条运维指令</p>
<p>1、切换某一个Raft Group的Leader节点</p>
<pre><code class="language-java">POST /nacos/v1/core/ops/raft
{
    <span class="hljs-string">"groupId"</span>: <span class="hljs-string">"xxx"</span>,
    <span class="hljs-string">"command"</span>: <span class="hljs-string">"transferLeader"</span>
    <span class="hljs-string">"value"</span>: <span class="hljs-string">"ip:{raft_port} or ip:{raft_port},ip:{raft_port},ip:{raft_port}"</span>
}
</code></pre>
<p><a name="Fs7VE"></a>
2、重置某一个Raft Group的集群成员</p>
<pre><code class="language-java">POST /nacos/v1/core/ops/raft
{
    <span class="hljs-string">"groupId"</span>: <span class="hljs-string">"xxx"</span>,
    <span class="hljs-string">"command"</span>: <span class="hljs-string">"resetRaftCluster"</span>,
    <span class="hljs-string">"value"</span>: <span class="hljs-string">"ip:{raft_port},ip:{raft_port},ip:{raft_port},ip:{raft_port}"</span>
}
</code></pre>
<p>注意，该操作是一个高危操作，仅仅当Raft集群的 n/2 + 1节点crash之后无法满足过半投票的要求才可以使用该运维命令，用于快速让当前剩余的节点重组Raft集群，对外提供服务，但是这个操作很大程度会造成数据的丢失<br /></p>
<p><a name="VfG5T"></a>
3、触发某一个Raft Group执行快照操作</p>
<pre><code class="language-java">POST /nacos/v1/core/ops/raft
{
    <span class="hljs-string">"groupId"</span>: <span class="hljs-string">"xxx"</span>,
    <span class="hljs-string">"command"</span>: <span class="hljs-string">"doSnapshot"</span>,
    <span class="hljs-string">"value"</span>: <span class="hljs-string">"ip:{raft_port}"</span>
}
</code></pre>
<p><a name="m9LfI"></a>
4、移除某一个Raft Group中的某一成员</p>
<pre><code class="language-java">POST /nacos/v1/core/ops/raft
{
    <span class="hljs-string">"groupId"</span>: <span class="hljs-string">"xxx"</span>,
    <span class="hljs-string">"command"</span>: <span class="hljs-string">"removePeer"</span>,
    <span class="hljs-string">"value"</span>: <span class="hljs-string">"ip:{raft_port}"</span>
}
</code></pre>
<p><a name="ev3MW"></a>
5、批量移除某一个Raft Group中的多个成员</p>
<pre><code class="language-java">POST /nacos/v1/core/ops/raft
{
    <span class="hljs-string">"groupId"</span>: <span class="hljs-string">"xxx"</span>,
    <span class="hljs-string">"command"</span>: <span class="hljs-string">"removePeers"</span>,
    <span class="hljs-string">"value"</span>: <span class="hljs-string">"ip:{raft_port},ip:{raft_port},ip:{raft_port},..."</span>
}
</code></pre>
<p><a name="GzMuP"></a></p>
<h3>JRaft协议相关配置参数</h3>
<pre><code class="language-yaml"><span class="hljs-comment">### Sets the Raft cluster election timeout, default value is 5 second</span>
<span class="hljs-string">nacos.core.protocol.raft.data.election_timeout_ms=5000</span>
<span class="hljs-comment">### Sets the amount of time the Raft snapshot will execute periodically, default is 30 minute</span>
<span class="hljs-string">nacos.core.protocol.raft.data.snapshot_interval_secs=30</span>
<span class="hljs-comment">### Requested retries, default value is 1</span>
<span class="hljs-string">nacos.core.protocol.raft.data.request_failoverRetries=1</span>
<span class="hljs-comment">### raft internal worker threads</span>
<span class="hljs-string">nacos.core.protocol.raft.data.core_thread_num=8</span>
<span class="hljs-comment">### Number of threads required for raft business request processing</span>
<span class="hljs-string">nacos.core.protocol.raft.data.cli_service_thread_num=4</span>
<span class="hljs-comment">### raft linear read strategy, defaults to index</span>
<span class="hljs-string">nacos.core.protocol.raft.data.read_index_type=ReadOnlySafe</span>
<span class="hljs-comment">### rpc request timeout, default 5 seconds</span>
<span class="hljs-string">nacos.core.protocol.raft.data.rpc_request_timeout_ms=5000</span>
</code></pre>
<h4>线性读参数解析</h4>
<ol>
<li><strong>ReadOnlySafe</strong>
<ol>
<li>该线性读模式，每次Follower进行读请求时，需要和Leader同步日志提交位点信息，而Leader，需要向过半的Follower发起证明自己是Leader的轻量的RPC请求，相当于一个Follower读，至少需要1 + （n/2）+ 1 次的RPC请求。</li>
</ol>
</li>
<li><strong>ReadOnlyLeaseBased</strong>
<ol>
<li>该线性读模式，每次Follower进行读请求时，Leader只需要判断自己的Leader租约是否过期了，如果没有过期，直接可以回复Follower自己是Leader，但是该机制对于机器时钟要求很严格，如果有做时钟同步的话，可以考虑使用该线性读模式。</li>
</ol>
</li>
</ol>
<p><a name="WiLDa"></a></p>
<h2>Nacos内嵌分布式ID</h2>
<p>nacos内嵌的分布式ID为Snakeflower，dataCenterId默认为1，workerId的值计算方式如下</p>
<pre><code class="language-yaml"><span class="hljs-string">InetAddress</span> <span class="hljs-string">address;</span>
<span class="hljs-string">try</span> <span class="hljs-string">{</span>
	<span class="hljs-string">address</span> <span class="hljs-string">=</span> <span class="hljs-string">InetAddress.getLocalHost();</span>
<span class="hljs-string">}</span> <span class="hljs-string">catch</span> <span class="hljs-string">(final</span> <span class="hljs-string">UnknownHostException</span> <span class="hljs-string">e)</span> <span class="hljs-string">{</span>
	<span class="hljs-string">throw</span> <span class="hljs-string">new</span> <span class="hljs-string">IllegalStateException(</span>
						<span class="hljs-string">"Cannot get LocalHost InetAddress, please check your network!"</span><span class="hljs-string">);</span>
<span class="hljs-string">}</span>
<span class="hljs-string">byte[]</span> <span class="hljs-string">ipAddressByteArray</span> <span class="hljs-string">=</span> <span class="hljs-string">address.getAddress();</span>
<span class="hljs-string">workerId</span> <span class="hljs-string">=</span> <span class="hljs-string">(((ipAddressByteArray[ipAddressByteArray.length</span> <span class="hljs-bullet">-</span> <span class="hljs-number">2</span><span class="hljs-string">]</span> <span class="hljs-string">&amp;</span> <span class="hljs-number">0</span><span class="hljs-string">B11)</span>
					<span class="hljs-string">&lt;&lt;</span> <span class="hljs-string">Byte.SIZE)</span> <span class="hljs-string">+</span> <span class="hljs-string">(ipAddressByteArray[ipAddressByteArray.length</span> <span class="hljs-bullet">-</span> <span class="hljs-number">1</span><span class="hljs-string">]</span>
					<span class="hljs-string">&amp;</span> <span class="hljs-number">0xFF</span><span class="hljs-string">));</span>
</code></pre>
<p>如果需要手动指定dataCenterId以及workerId，则在application.properties或者启动时添加命令行参数</p>
<pre><code class="language-yaml"><span class="hljs-comment">### set the dataCenterID manually</span>
<span class="hljs-comment"># nacos.core.snowflake.data-center=</span>
<span class="hljs-comment">### set the WorkerID manually</span>
<span class="hljs-comment"># nacos.core.snowflake.worker-id=</span>
</code></pre>
<p><a name="ZLp5w"></a></p>
<h2>Nacos内嵌的轻量的基于Derby的分布式关系型存储</h2>
<p><a name="1B5KV"></a></p>
<h3>背景</h3>
<ol>
<li>如果配置文件数量较少，在集群模式下需要高可用数据库集群作为支撑的成本太大，期望有一个轻量的分布式关系型存储来解决</li>
<li>nacos内部一些元数据信息存储，比如用户信息，命名空间信息</li>
<li>思路来源：<a href="https://github.com/rqlite/rqlite">https://github.com/rqlite/rqlite</a><br />
<a name="Du2qc"></a></li>
</ol>
<h3>设计思路</h3>
<p><a name="NzxHa"></a></p>
<h4>目标</h4>
<p>设计目标，是期望nacos存在两种数据存储模式，一种是现在的方式，数据存储在外置数据源（关系型数据库）；第二种方式是内嵌存储数据源（Apache Derby）。用户能够使用命令行参数配置的方式，随意使用这两种数据存储模式<br /><img src="https://cdn.nlark.com/yuque/0/2020/png/333972/1591015542106-f14d2579-229f-4bfb-a432-e40854e65d6d.png#align=left&amp;display=inline&amp;height=903&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=903&amp;originWidth=1514&amp;size=237497&amp;status=done&amp;style=none&amp;width=1514" alt="image.png">
<a name="LqUtU"></a></p>
<h4>总体</h4>
<p>将一次请求操作涉及的SQL上下文按顺序保存起来。然后通过一致协议层将本次请求涉及的SQL上下文进行同步，然后每个节点将其解析并重新按顺序在一次数据库会话中执行。<br /><img src="https://cdn.nlark.com/yuque/0/2020/png/333972/1587204104465-2270480a-de25-4c84-a11b-6edd2de99e66.png#align=left&amp;display=inline&amp;height=814&amp;margin=%5Bobject%20Object%5D&amp;name=%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%20%281%29.png&amp;originHeight=814&amp;originWidth=1149&amp;size=130886&amp;status=done&amp;style=none&amp;width=1149" alt="未命名文件 (1).png">
<a name="yxFYn"></a></p>
<h4>谁可以处理请求</h4>
<p>当使用者开启1.3.0的新特性——内嵌分布式关系型数据存储时，所有的写操作请求都将路由到Leader节点进行处理；但是，由于Raft状态机的特性，当某一个节点在apply数据库操作请求时发生非SQL逻辑错误引发的异常时，将导致状态机无法继续正常进行工作，此时将会触发配置管理模块的降级操作</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">registerSubscribe</span><span class="hljs-params">()</span> </span>{
	NotifyCenter.registerSubscribe(<span class="hljs-keyword">new</span> SmartSubscribe() {

		<span class="hljs-meta">@Override</span>
		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onEvent</span><span class="hljs-params">(Event event)</span> </span>{
			<span class="hljs-keyword">if</span> (event <span class="hljs-keyword">instanceof</span> RaftDBErrorRecoverEvent) {
				downgrading = <span class="hljs-keyword">false</span>;
				<span class="hljs-keyword">return</span>;
			}
			<span class="hljs-keyword">if</span> (event <span class="hljs-keyword">instanceof</span> RaftDBErrorEvent) {
				downgrading = <span class="hljs-keyword">true</span>;
			}
		}

		<span class="hljs-meta">@Override</span>
		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">canNotify</span><span class="hljs-params">(Event event)</span> </span>{
			<span class="hljs-keyword">return</span> (event <span class="hljs-keyword">instanceof</span> RaftDBErrorEvent) || (event <span class="hljs-keyword">instanceof</span> RaftDBErrorRecoverEvent);
		}
	});
}
</code></pre>
<p>因此，综上所述，可以通过活动图来理解下，什么情况下需要将请求进行转发
<img src="https://cdn.nlark.com/yuque/__puml/db53c1ade61235d4c9659607b98ef7c6.svg#lake_card_v2=eyJjb2RlIjoiQHN0YXJ0dW1sXG5cbigqKSAtLT4gXCJGaWx0ZXIuZG9GaWx0ZXJcIlxuXG5pZiBcIuacrOiKgueCueaYr0xlYWRlclwiIHRoZW5cbiAgLWxlZnQtPlt0cnVlXSBcIuacrOiKgueCueWkhOeQhlwiXG4gIC0tPiAoKilcbmVsc2VcbiAgICBpZiBcIuezu-e7n-mZjee6p-W8gOWQr1wiIHRoZW5cbiAgICAgICAgLS0-W3RydWVdIFwi6L2s5Y-R57uZTGVhZGVyXCJcbiAgICAgICAgLS0-ICgqKVxuICAgIGVsc2VcbiAgICAgICAgaWYgXCLlhpnmk43kvZzor7fmsYJcIiB0aGVuXG4gICAgICAgICAgICAtLT5bdHJ1ZV0gXCLovazlj5Hnu5lMZWFkZXJcIlxuICAgICAgICAgICAgLS0-ICgqKVxuICAgICAgICBlbHNlXG4gICAgICAgICAgICAtLT5bZmFsc2VdIFwi5pys6IqC54K55aSE55CGXCJcbiAgICAgICAgZW5kaWZcbiAgICBlbmRpZlxuZW5kaWZcblxuQGVuZHVtbCIsInR5cGUiOiJwdW1sIiwibWFyZ2luIjp0cnVlLCJpZCI6IjFuWW9HIiwidXJsIjoiaHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlL19fcHVtbC9kYjUzYzFhZGU2MTIzNWQ0Yzk2NTk2MDdiOThlZjdjNi5zdmciLCJjYXJkIjoiZGlhZ3JhbSJ9" alt=""><a name="g1buf"></a></p>
<h4>相关数据承载对象</h4>
<p>数据库的DML语句是select、insert、update、delete，根据SQL语句对于数据操作的性质，可以分为两大类：query以及update，select语句对应的是数据查询，insert、update、delete语句对应的是数据修改。同时在进行数据库操作时，为了避免SQL注入，使用的是PreparedStatement，因此需要SQL语句+参数，因此可以得到两个关于数据库操作的Request对象</p>
<ol>
<li>SelectRequest</li>
</ol>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectRequest</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Serializable</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> serialVersionUID = <span class="hljs-number">2212052574976898602L</span>;
    <span class="hljs-comment">// 查询类别，因为目前使用的是JdbcTemplate，查询单个、查询多个，是否使用RowMapper转为对象</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">byte</span> queryType;
    <span class="hljs-comment">// sql语句</span>
    <span class="hljs-comment">// select * from config_info where</span>
    <span class="hljs-keyword">private</span> String sql;
    <span class="hljs-keyword">private</span> Object[] args;
    <span class="hljs-keyword">private</span> String className;
}
</code></pre>
<ol start="2">
<li>ModifyRequest</li>
</ol>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ModifyRequest</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Serializable</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> serialVersionUID = <span class="hljs-number">4548851816596520564L</span>;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> executeNo;
    <span class="hljs-keyword">private</span> String sql;
    <span class="hljs-keyword">private</span> Object[] args;
}
</code></pre>
<p><a name="moKl7"></a></p>
<h4>配置发布</h4>
<p>配置发布操作涉及三个事务：</p>
<ol>
<li>config_info保存配置信息</li>
<li>config_tags_relation保存配置与标签的关联关系</li>
<li>his_config_info保存一条配置操作历史记录</li>
</ol>
<p>这三个事务都在配置发布这个大事务下，如果说我们对每个事务操作进行一个Raft协议提交，假设1、2两个事务通过Raft提交后都成功Apply了，第三个事务在进行Raft提交后apply失败，那么对于这个配置发布的大事务来说，是需要整体回滚的，否则就会违反原子性，那么可能需要说将事务回滚操作又进行一次Raft提交，那么整体的复杂程度上升，并且直接引入了分布式事务的管理，因此为了避免这个问题，我们将这三个事务涉及的SQL上下文进行整合成一个大的SQL上下文，对这大的SQL上下文进行Raft协议提交。保证了三个子事务在同一次数据库会话当中，成功解决原子性的问题，同时由于Raft协议对于事务日志的处理是串行执行的，因此相当于将数据库的事务隔离级别调整为串行化。</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addConfigInfo</span><span class="hljs-params">(<span class="hljs-keyword">final</span> String srcIp,
			<span class="hljs-keyword">final</span> String srcUser, <span class="hljs-keyword">final</span> ConfigInfo configInfo, <span class="hljs-keyword">final</span> Timestamp time,
			<span class="hljs-keyword">final</span> Map&lt;String, Object&gt; configAdvanceInfo, <span class="hljs-keyword">final</span> <span class="hljs-keyword">boolean</span> notify)</span> </span>{

	<span class="hljs-keyword">try</span> {
		<span class="hljs-keyword">final</span> String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ?
					StringUtils.EMPTY :
					configInfo.getTenant();
		configInfo.setTenant(tenantTmp);
        
        <span class="hljs-comment">// 通过雪花ID算法获取数据库主键</span>
		<span class="hljs-keyword">long</span> configId = idGeneratorManager.nextId(RESOURCE_CONFIG_INFO_ID);
		<span class="hljs-keyword">long</span> hisId = idGeneratorManager.nextId(RESOURCE_CONFIG_HISTORY_ID);

		addConfigInfoAtomic(configId, srcIp, srcUser, configInfo, time,
					configAdvanceInfo);
		String configTags = configAdvanceInfo == <span class="hljs-keyword">null</span> ?
					<span class="hljs-keyword">null</span> :
					(String) configAdvanceInfo.get(<span class="hljs-string">"config_tags"</span>);

		addConfigTagsRelation(configId, configTags, configInfo.getDataId(),
					configInfo.getGroup(), configInfo.getTenant());
		insertConfigHistoryAtomic(hisId, configInfo, srcIp, srcUser, time, <span class="hljs-string">"I"</span>);
		EmbeddedStorageContextUtils.onModifyConfigInfo(configInfo, srcIp, time);
		databaseOperate.blockUpdate();
	}
	<span class="hljs-keyword">finally</span> {
		EmbeddedStorageContextUtils.cleanAllContext();
	}
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> <span class="hljs-title">addConfigInfoAtomic</span><span class="hljs-params">(<span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> id, <span class="hljs-keyword">final</span> String srcIp,
			<span class="hljs-keyword">final</span> String srcUser, <span class="hljs-keyword">final</span> ConfigInfo configInfo, <span class="hljs-keyword">final</span> Timestamp time,
			Map&lt;String, Object&gt; configAdvanceInfo)</span> </span>{
	...
    <span class="hljs-comment">// 参数处理</span>
    ...
	<span class="hljs-keyword">final</span> String sql =
				<span class="hljs-string">"INSERT INTO config_info(id, data_id, group_id, tenant_id, app_name, content, md5, src_ip, src_user, gmt_create,"</span>
						+ <span class="hljs-string">"gmt_modified, c_desc, c_use, effect, type, c_schema) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"</span>;
	<span class="hljs-keyword">final</span> Object[] args = <span class="hljs-keyword">new</span> Object[] { id, configInfo.getDataId(),
				configInfo.getGroup(), tenantTmp, appNameTmp, configInfo.getContent(),
				md5Tmp, srcIp, srcUser, time, time, desc, use, effect, type, schema, };
	SqlContextUtils.addSqlContext(sql, args);
	<span class="hljs-keyword">return</span> id;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addConfigTagRelationAtomic</span><span class="hljs-params">(<span class="hljs-keyword">long</span> configId, String tagName, String dataId,
			String group, String tenant)</span> </span>{
	<span class="hljs-keyword">final</span> String sql =
				<span class="hljs-string">"INSERT INTO config_tags_relation(id,tag_name,tag_type,data_id,group_id,tenant_id) "</span>
						+ <span class="hljs-string">"VALUES(?,?,?,?,?,?)"</span>;
	<span class="hljs-keyword">final</span> Object[] args = <span class="hljs-keyword">new</span> Object[] { configId, tagName, <span class="hljs-keyword">null</span>, dataId, group,
				tenant };
	SqlContextUtils.addSqlContext(sql, args);
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">insertConfigHistoryAtomic</span><span class="hljs-params">(<span class="hljs-keyword">long</span> configHistoryId, ConfigInfo configInfo,
			String srcIp, String srcUser, <span class="hljs-keyword">final</span> Timestamp time, String ops)</span> </span>{
	...
    <span class="hljs-comment">// 参数处理</span>
    ...
	<span class="hljs-keyword">final</span> String sql =
				<span class="hljs-string">"INSERT INTO his_config_info (id,data_id,group_id,tenant_id,app_name,content,md5,"</span>
						+ <span class="hljs-string">"src_ip,src_user,gmt_modified,op_type) VALUES(?,?,?,?,?,?,?,?,?,?,?)"</span>;
	<span class="hljs-keyword">final</span> Object[] args = <span class="hljs-keyword">new</span> Object[] { configHistoryId, configInfo.getDataId(),
				configInfo.getGroup(), tenantTmp, appNameTmp, configInfo.getContent(),
				md5Tmp, srcIp, srcUser, time, ops };

	SqlContextUtils.addSqlContext(sql, args);
}

<span class="hljs-comment">/**
 * Temporarily saves all insert, update, and delete statements under
 * a transaction in the order in which they occur
 *
 * <span class="hljs-doctag">@author</span> &lt;a href="mailto:liaochuntao@live.com"&gt;liaochuntao&lt;/a&gt;
 */</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SqlContextUtils</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> ThreadLocal&lt;ArrayList&lt;ModifyRequest&gt;&gt; SQL_CONTEXT =
            ThreadLocal.withInitial(ArrayList::<span class="hljs-keyword">new</span>);

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addSqlContext</span><span class="hljs-params">(String sql, Object... args)</span> </span>{
        ArrayList&lt;ModifyRequest&gt; requests = SQL_CONTEXT.get();
        ModifyRequest context = <span class="hljs-keyword">new</span> ModifyRequest();
        context.setExecuteNo(requests.size());
        context.setSql(sql);
        context.setArgs(args);
        requests.add(context);
        SQL_CONTEXT.set(requests);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List&lt;ModifyRequest&gt; <span class="hljs-title">getCurrentSqlContext</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> SQL_CONTEXT.get();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">cleanCurrentSqlContext</span><span class="hljs-params">()</span> </span>{
        SQL_CONTEXT.remove();
    }

}
</code></pre>
<p>通过一个时序图来更加直观的理解
<img src="https://cdn.nlark.com/yuque/__puml/618e8997395fbc74433ac4a60f67b6b4.svg#lake_card_v2=eyJjb2RlIjoiQHN0YXJ0dW1sXG5cbmF1dG9udW1iZXJcblxuYWN0b3IgXCJVc2VyXCIgYXMgVXNlclxuXG5wYXJ0aWNpcGFudCBcIkNvbmZpZ1NlcnZpY2VcIiBhcyBTZXJ2aWNlXG5wYXJ0aWNpcGFudCBcIlBlcnNpc3RlbmNlU2VydmljZVwiIGFzIFJlcG9zaXRvcnlcbnBhcnRpY2lwYW50IFwiRGVyYnlcIiBhcyBEQlxucGFydGljaXBhbnQgXCJTUUxDb250ZXh0VXRpbHNcIiBhcyBTUUxDb250ZXh0XG5wYXJ0aWNpcGFudCBcIkxvZ1Byb2Nlc3NvclwiIGFzIFByb2Nlc3NvclxucGFydGljaXBhbnQgXCJDb25zaXN0ZW5jeVByb3RvY29sXCIgYXMgUHJvdG9jb2xcbnBhcnRpY2lwYW50IFwiTG9nXCIgYXMgTG9nXG5cbmFjdGl2YXRlIFVzZXJcblxuVXNlciAtPiBTZXJ2aWNlOiDlj5HotbfphY3nva7lj5HluIPor7fmsYJcbmFjdGl2YXRlIFNlcnZpY2VcblxuU2VydmljZSAtPiBSZXBvc2l0b3J5OiDphY3nva7lj5HluINcbmFjdGl2YXRlIFJlcG9zaXRvcnlcblxuUmVwb3NpdG9yeSAtPiBTUUxDb250ZXh0OiDmi6bmiKrlvZPliY1TUUzkuIrkuIvmlodcbmFjdGl2YXRlIFNRTENvbnRleHRcblxuUmVwb3NpdG9yeSAtPiBSZXBvc2l0b3J5OiBjb21taXQg5pON5L2cXG5cblNRTENvbnRleHQgLT4gUmVwb3NpdG9yeTog5b2T5YmN6K-35rGC5raJ5Y-K55qE5omA5pyJU1FM5LiK5LiL5paHXG5cbmRlYWN0aXZhdGUgU1FMQ29udGV4dFxuXG5SZXBvc2l0b3J5IC0-IExvZzog5p6E5bu66K-35rGC5L2TXG5cbmFjdGl2YXRlIExvZ1xuZGVhY3RpdmF0ZSBMb2dcblxuUmVwb3NpdG9yeSAtPiBQcm90b2NvbDogc3VibWl0KCkg6L-b6KGM6K-35rGC5o-Q5LqkXG5hY3RpdmF0ZSBQcm90b2NvbFxuZGVhY3RpdmF0ZSBSZXBvc2l0b3J5XG5cblByb3RvY29sIC0-IFByb2Nlc3Nvcjogb25BcHBseSgpIOaWueazle-8jOeKtuaAgeacuuWkjeWItkxvZ1xuYWN0aXZhdGUgUHJvY2Vzc29yXG5cblByb2Nlc3NvciAtPiBSZXBvc2l0b3J5OiDmiafooYzmiYDmnInnmoRTUUzkuIrkuIvmlodcbmFjdGl2YXRlIFJlcG9zaXRvcnlcblxuUmVwb3NpdG9yeSAtPiBEQjog5pWw5o2u6JC95bqTXG5hY3RpdmF0ZSBEQlxuXG5EQiAtPiBSZXBvc2l0b3J5OiDnu5PmnZ_lubbov5Tlm57nu5PmnpxcbmRlYWN0aXZhdGUgREJcblxuUmVwb3NpdG9yeSAtPiBQcm9jZXNzb3I6IOi_lOWbnkxvZ0Z1dHVyZeW5tuiuvue9ruacrOasoeaJp-ihjOeahOe7k-aenFxuZGVhY3RpdmF0ZSBSZXBvc2l0b3J5XG5cblByb2Nlc3NvciAtPiBQcm90b2NvbDog6L-U5ZueTG9nRnV0dXJlXG5kZWFjdGl2YXRlIFByb2Nlc3NvclxuXG5Qcm90b2NvbCAtPiBTZXJ2aWNlOiDmnKzmrKHor7fmsYLnmoTnu5PmnpxcbmRlYWN0aXZhdGUgUHJvdG9jb2xcblxuU2VydmljZSAtPiBVc2VyXG5kZWFjdGl2YXRlIFNlcnZpY2VcbmRlYWN0aXZhdGUgVXNlclxuXG5AZW5kdW1sIiwidHlwZSI6InB1bWwiLCJtYXJnaW4iOnRydWUsImlkIjoiVnQxNzciLCJ1cmwiOiJodHRwczovL2Nkbi5ubGFyay5jb20veXVxdWUvX19wdW1sLzYxOGU4OTk3Mzk1ZmJjNzQ0MzNhYzRhNjBmNjdiNmI0LnN2ZyIsImNhcmQiOiJkaWFncmFtIn0=" alt=""><a name="AAJTE"></a></p>
<h3>如何使用新特性</h3>
<pre><code class="language-bash">./startup.sh -p embedded
</code></pre>
<p>是否启用内嵌的分布式关系型存储的活动图
<img src="https://cdn.nlark.com/yuque/__puml/1fd656ba3b39fe5de8fea78efdf98dd1.svg#lake_card_v2=eyJjb2RlIjoiQHN0YXJ0dW1sXG5cbigqKSAtLT4gXCJDb25maWcuc3RhcnQoKVwiXG5cbmlmIFwi5Y2V5py65qih5byPXCIgdGhlblxuICAtcmlnaHQtPlt0cnVlXSBcInJldHVybiDpu5jorqTljZXmnLrlrZjlgqjooYzkuLpcIlxuICAtLT4gKCopXG5lbHNlXG4gaWYgXCLlt7LphY3nva7lpJbnva7lrZjlgqhcIiB0aGVuXG4gICAgLS0-W3RydWVdIFwicmV0dXJuIOWklue9ruaVsOaNruWtmOWCqOihjOS4ulwiXG4gICAgLS0-ICgqKVxuIGVsc2VcbiAgICBpZiBcIuW8gOWQr-WGheW1jOWtmOWCqFwiIHRoZW5cbiAgICAgICAgLS0-W3RydWVdIFwicmV0dXJuIOi9u-mHj-WGheW1jOWIhuW4g-W8j-WtmOWCqOihjOS4ulwiXG4gICAgICAgIC0tPiAoKilcbiAgICBlbHNlXG4gICAgICAgIC0-W2ZhbHNlXSBcInJldHVybiDpu5jorqTlpJbnva7mlbDmja7lrZjlgqjooYzkuLpcIlxuICAgICAgICAtLT4gKCopXG5cdFx0ZW5kaWZcbiBlbmRpZlxuZW5kaWZcblxuQGVuZHVtbCIsInR5cGUiOiJwdW1sIiwibWFyZ2luIjp0cnVlLCJpZCI6IkNNN1VDIiwidXJsIjoiaHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlL19fcHVtbC8xZmQ2NTZiYTNiMzlmZTVkZThmZWE3OGVmZGY5OGRkMS5zdmciLCJjYXJkIjoiZGlhZ3JhbSJ9" alt=""><a name="9UAXN"></a></p>
<h3>新特性的相关运维操作</h3>
<p>直接查询每个节点的derby存储的数据</p>
<pre><code class="language-java">GET /nacos/v1/cs/ops/derby?sql=select * from config_info

<span class="hljs-keyword">return</span> List&lt;Map&lt;String, Object&gt;&gt;
</code></pre>
<p><a name="YcqO2"></a></p>
<h3>不足</h3>
<ol>
<li>在数据库上层构建一层分布式数据操作同步层，对数据库的操作存在了限制，比如第一步insert操作，然后select操作，最后在update操作，这种在数据修改语句中穿插着查询语句的操作顺序是不支持的</li>
<li>限制了数据库的性能，由于间接的将数据库事务隔离级别调整为了串行化，人为的将并发能力降低了
<a name="7dul8"></a></li>
</ol>
<h3>未来演进</h3>
<p>将于Apache Derby官方一起尝试基于Raft实现BingLog的同步复制操作，从底层实现数据库同步能力</p>
</section><footer class="footer-container" data-reactid="29"><div class="footer-body" data-reactid="30"><img src="/img/nacos_gray.png" data-reactid="31"/><div class="cols-container" data-reactid="32"><div class="col col-12" data-reactid="33"><h3 data-reactid="34">愿景</h3><p data-reactid="35">Nacos 通过提供简单易用的动态服务发现、服务配置、服务共享与管理等服务基础设施，帮助用户在云原生时代，在私有云、混合云或者公有云等所有云环境中，更好的构建、交付、管理自己的微服务平台，更快的复用和组合业务服务，更快的交付商业创新的价值，从而为用户赢得市场。</p></div><div class="col col-6" data-reactid="36"><dl data-reactid="37"><dt data-reactid="38">文档</dt><dd data-reactid="39"><a href="/zh-cn/docs/what-is-nacos.html" target="_self" data-reactid="40">概览</a></dd><dd data-reactid="41"><a href="/zh-cn/docs/quick-start.html" target="_self" data-reactid="42">快速开始</a></dd><dd data-reactid="43"><a href="/zh-cn/docs/contributing.html" target="_self" data-reactid="44">开发者指南</a></dd></dl></div><div class="col col-6" data-reactid="45"><dl data-reactid="46"><dt data-reactid="47">资源</dt><dd data-reactid="48"><a href="/zh-cn/community/index.html" target="_self" data-reactid="49">社区</a></dd><dd data-reactid="50"><a href="https://www.aliyun.com/product/acm?source_type=nacos_pc_20181219" target="_self" data-reactid="51">云服务 ACM</a></dd><dd data-reactid="52"><a href="https://www.aliyun.com/product/edas?source_type=nacos_pc_20181219" target="_self" data-reactid="53">云服务 EDAS</a></dd><dd data-reactid="54"><a href="https://www.aliyun.com/product/ahas?source_type=nacos_pc_20190225" target="_self" data-reactid="55">云服务 AHAS</a></dd></dl></div></div><div class="copyright" data-reactid="56"><span data-reactid="57">@ 2018 The Nacos Authors | An Alibaba Middleware (Aliware) Project</span></div></div></footer></div></div>
	<script src="https://f.alicdn.com/react/15.4.1/react-with-addons.min.js"></script>
	<script src="https://f.alicdn.com/react/15.4.1/react-dom.min.js"></script>
	<script>
		window.rootPath = '';
  </script>
	<script src="/build/blogDetail.js"></script>
</body>
</html>